package com.tg.app.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.GestureDetectorCompat;

import com.tange.base.toolkit.DimenUtil;
import com.tg.app.R;
import com.tg.appcommon.android.Packet;
import com.tg.appcommon.android.TGLog;
import com.tg.data.http.entity.DetectionPoint;

import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

public class DetectionAreaView extends View {
    private static final String TAG = "DetectionAreaView";
    private static final int RADIUS = 30;
    List<DetectionPoint> points = new ArrayList<>();
    private GestureDetectorCompat gestureListener;
    private Paint paint;
    private  Paint mDottedLinePaint ;
    int color = Color.parseColor("#FB5D43");
    boolean isSelected = true;
    boolean inside = false;
    float w = 10000.0f;
    private int width = 0;
    private int height = 0;
    private DetectionAreaChangeListener detectionAreaChangeListener;

    public void setDetectionAreaChangeListener(DetectionAreaChangeListener detectionAreaChangeListener) {
        this.detectionAreaChangeListener = detectionAreaChangeListener;
    }

    public void setInfo(){
        width = getWidth();
        height = getHeight();

        if (width == 0){
            width = getContext().getResources().getDisplayMetrics().widthPixels - DimenUtil.dp2px(getContext(), 20);
        }

        if (height == 0){
            height = (int)getContext().getResources().getDimension(R.dimen.detection_area_height);
        }

    }
    public DetectionAreaView(@NonNull Context context) {
        super(context);
        init(context);
    }

    public void clear() {
        points.clear();
        invalidate();
    }

    public DetectionAreaView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public DetectionAreaView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }
    public int size(){
        return points.size();
    }
    public void setPoints(@NonNull List<DetectionPoint> p) {
        this.points.addAll(p);

        for (DetectionPoint point :points){
            int dx = point.x;
            int dy = point.y;
            if (dx != -1 && dy != -1){
                point.dx = dx*getWidth()/w;
                point.dy = dy*getHeight()/w;
                point.x = halfUp(point.dx);
                point.y = halfUp(point.dy);

                TGLog.d(TAG, "=》 "+getWidth()+"  "+ dx*getWidth()+" x: "+ point.x+" y:"+ point.y +" dx: "+dx+" dy: "+dy);
            }
        }
        invalidate();

    }
    private void init(Context context) {
        int width = 10;
        gestureListener = new GestureDetectorCompat(context, new GestureListener());
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(color);
        paint.setStrokeWidth(width);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);

        mDottedLinePaint = new Paint();
        mDottedLinePaint.setColor(color);
        mDottedLinePaint.setStyle(Paint.Style.STROKE);
        mDottedLinePaint.setStrokeWidth(width);
        PathEffect effects = new DashPathEffect(new float[]{5, 5}, 1);//意思是所画虚线规则是先画10个长度的实线，留下5个长度的空白
        mDottedLinePaint.setPathEffect(effects);

    }

    @SuppressLint({"LogConditional", "ClickableViewAccessibility"})
    @Override
    public boolean onTouchEvent(@SuppressLint("UnknownNullness") MotionEvent event) {
        if (event.getPointerCount() == 1) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                isSelected = false;
            }
            gestureListener.onTouchEvent(event);
            return true;
        }
        isSelected = false;

        return false;
    }

    public int getAreaCount(){
        if (points.size() == 0){
            return 0;
        }
        int count = 1;
        for (int i = 0; i < points.size(); i++) {
            DetectionPoint point = points.get(i);
            if (point.x == -1 && point.y == -1){
                count++;
            }
        }
        return count;
    }

    private List<DetectionPoint> getLastList(){
        List<DetectionPoint> pointList = new ArrayList<>();
        for (int i = points.size() - 1; i > -1; i--) {
            DetectionPoint point = points.get(i);
            if (point.x == -1 && point.y == -1){
                break;
            }
            pointList.add(point);
        }
        List<DetectionPoint> result = new ArrayList<>();
        for (int i = pointList.size() - 1; i > -1; i--){
            result.add(pointList.get(i));
        }
        return result;
    }


    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);
        @SuppressLint("DrawAllocation") Path path = new Path();
        drawPathLine(canvas, path, points);
        int pos = -1;
        for (int i = points.size() - 1; i > -1; i--) {
            DetectionPoint point = points.get(i);
            if (point.x == -1 && point.y == -1){
                pos = i;
                break;
            }
        }

        if (pos == -1){
            drawCircle(canvas, points);
            return;
        }
        @SuppressLint("DrawAllocation") List<DetectionPoint> pointList = new ArrayList<>();
        for (int i = pos + 1; points.size() > i; i++) {
            DetectionPoint point = points.get(i);
            pointList.add(point);
        }

        drawCircle(canvas, pointList);
    }

    private void drawPathLine(Canvas canvas, Path path, List<DetectionPoint> points) {
        int first = 0;
        for (int i = 0; points.size() > i; i++) {
            if (i == points.size() - 1){
                drawPathLineImpl(canvas, path, points, first, points.size());
                break;
            }
            DetectionPoint point = points.get(i);
            if (point.x == -1 && point.y == -1){
                drawPathLineImpl(canvas, path, points, first, i);
                first = i + 1;
            }

        }
    }

    private void drawPathLineImpl(Canvas canvas, Path path, List<DetectionPoint> points, int start, int end) {
        Paint colorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        colorPaint.setColor(color);
        colorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        colorPaint.setAlpha(255 * 3 / 10);
        if (end > start) {
            for (int i = start; end > i; i++) {
                DetectionPoint point = points.get(i);
                if (i == start) {
                    path.moveTo(point.x, point.y);
                } else {
                    path.lineTo(point.x, point.y);
                }
                if (i > start) {
                    DetectionPoint startPoint = points.get(i - 1);
                    canvas.drawLine(startPoint.x, startPoint.y, point.x, point.y, paint);
                    if (i == end - 1) {
                        DetectionPoint endPoint = points.get(start);
                        canvas.drawLine(point.x, point.y, endPoint.x, endPoint.y, mDottedLinePaint);
                    }
                }
            }
            canvas.drawPath(path, colorPaint);
            path.close();
        }
    }

    private void drawCircle(Canvas canvas, List<DetectionPoint> points) {
        Paint p = new Paint();
        int r = RADIUS;
        LinearGradient lg = new LinearGradient(0, 0, r * 2, r * 2, Color.WHITE, Color.parseColor("#F5F8FF"), Shader.TileMode.MIRROR);
        p.setShader(lg);
        for (DetectionPoint point : points) {
            if (point.x == -1 && point.y == -1){
                continue;
            }
            canvas.drawCircle(point.x, point.y, r, p);
        }
    }


   private DetectionPoint isRangeOfView(List<DetectionPoint> points, float dx, float dy) {
        int r = RADIUS;;
        for (DetectionPoint point : points) {
            int ev_x = point.x;
            int ev_y = point.y;
            boolean isRange = (ev_x > dx - r) && (ev_x < dx + r) && (ev_y > dy - r) && (ev_y < dy + r);
            if (isRange) return point;
        }
        return null;
    }


    public boolean isIntersect(){
        int size = points.size();
        boolean intersect = false;
        if (size >= 4) {
            List<DetectionPoint> list = new ArrayList<>();
            for (int i = 0; i < size; i++){
                DetectionPoint point = points.get(i);
                if (point.x == -1 && point.y == -1){
                   if (list.size() > 3){
                       intersect = intersectPolygon(list);
                       if (intersect){
                           return true;
                       }
                   }
                   list.clear();
                }else{
                    list.add(point);
                }
            }
            if (list.size() > 0){
                intersect =  intersectPolygon(list);
            }

        }
        return  intersect;
    }

    @NonNull
    public byte[] getPolygonsData(){
        int count = getAreaCount();
        int num = points.size() - count + 1;
        ByteBuffer buffer = ByteBuffer.allocate( 2 + 4*count + num*8);
        byte[] size = Packet.shortToByteArray_Little((short) count);
        buffer.put(size);


        count = 0;
        int i = 0;
        for (; i < points.size(); i++){
            DetectionPoint point = points.get(i);
            if (point.x != -1 && point.y != -1){
                count++;
            }else {
                byte[] len = Packet.intToByteArray_Little(count);
                buffer.put(len);

                for (int j = i - count; j < i; j++){
                    point = points.get(j);
                    int pX= halfUp(point.dx*w/getWidth());
                    int pY= halfUp(point.dy*w/getHeight());
                    byte[] x = Packet.intToByteArray_Little(pX);
                    byte[] y = Packet.intToByteArray_Little(pY);
                    TGLog.d(TAG, "=》 getWidth(): "+getWidth()+" x: "+ point.x+" y:"+ point.y +" dx: "+pX +" dy: "+pY );
                    buffer.put(x);
                    buffer.put(y);
                }

                count = 0;
            }
        }

        if (count > 0){
            byte[] len = Packet.intToByteArray_Little(count);
            buffer.put(len);

            for (int j = i - count; j < i; j++){
                DetectionPoint point = points.get(j);
                int pX= halfUp(point.dx*w/getWidth());
                int pY= halfUp(point.dy*w/getHeight());
                byte[] x = Packet.intToByteArray_Little(pX);
                byte[] y = Packet.intToByteArray_Little(pY);
                TGLog.d(TAG, "=》 getWidth(): "+getWidth()+" x: "+ point.x+" y:"+ point.y +" dx: "+pX +" dy: "+pY );
                buffer.put(x);
                buffer.put(y);
            }
        }

        return  buffer.array();
    }

    private int halfUp(float v){
        BigDecimal b = new BigDecimal(v);
        return b.setScale(0,  BigDecimal.ROUND_HALF_UP).intValue();
    }

    public boolean inside(DetectionPoint p, List<DetectionPoint> list) {
        @SuppressLint("DrawAllocation") Path path = new Path();
        for (int i = 0; i < list.size(); i++){
            DetectionPoint point = list.get(i);
            if (i == 0) {
                path.moveTo(point.x, point.y);
            } else {
                path.lineTo(point.x, point.y);
            }
        }
        RectF bounds = new RectF();
        path.computeBounds(bounds, true);
        Region region = new Region();
        region.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom));
        return region.contains(p.x, p.y);

    }
    class GestureListener extends GestureDetector.SimpleOnGestureListener {
        private DetectionPoint mSelectPoint;
        @Override
        public boolean onDown(MotionEvent e) {
            float dx = e.getX();
            float dy = e.getY();
            List<DetectionPoint> list = getLastList();
            mSelectPoint = isRangeOfView(list, dx, dy);
            isSelected = mSelectPoint != null;
            if (!isSelected){
                DetectionPoint point = new DetectionPoint();
                point.dx = dx;
                point.dy = dy;
                point.x = (int)dx;
                point.y = (int)dy;
                inside = inside(point, list);

                TGLog.d(TAG, "inside = " + inside + ", point.x = " + point.x + ", point.y = " + point.y);
            }
            return super.onDown(e);
        }

        private boolean isOutpaceOfView(float distanceX, float distanceY){
            List<DetectionPoint> list = getLastList();
            setInfo();
            for (int i = 0; i < list.size(); i++) {
                DetectionPoint point = list.get(i);
                if (point.x - distanceX < RADIUS || point.y - distanceY < RADIUS || point.x - distanceX > width - RADIUS || point.y - distanceY > height - RADIUS) {
                    return true;
                }
            }
            return false;
        }
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (isSelected) {
                mSelectPoint.dx =  e2.getX();
                mSelectPoint.dy =  e2.getY();
                mSelectPoint.x = (int) e2.getX();
                mSelectPoint.y = (int) e2.getY();
                if (mSelectPoint.x<0||mSelectPoint.y<0||mSelectPoint.x>getWidth()||mSelectPoint.y>getHeight()){
                    points.remove(mSelectPoint);
                    if (points.size() > 0 && points.get(points.size() - 1).x == -1 && points.get(points.size() - 1).y == -1){
                        points.remove(points.size() - 1);
                    }
                    isSelected = false;
                }

//                if (mSelectPoint.x < RADIUS) mSelectPoint.x = RADIUS;
//                if (mSelectPoint.y < RADIUS) mSelectPoint.y = RADIUS;
//                if (mSelectPoint.x > width - RADIUS) mSelectPoint.x = width - RADIUS;
//                if (mSelectPoint.y > height - RADIUS) mSelectPoint.y = height - RADIUS;
                onChanged();
                invalidate();
            }else if (inside){
                if (!isOutpaceOfView(distanceX, distanceY)){
                    List<DetectionPoint> list = getLastList();
                    for (int i = 0; i < list.size(); i++){
                        DetectionPoint point = list.get(i);
                        point.dx -= distanceX;
                        point.dy -= distanceY;
                        point.x -= distanceX;
                        point.y -= distanceY;
                    }
                }

                onChanged();
                invalidate();
            }
            return super.onScroll(e1, e2, distanceX, distanceY);
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            List<DetectionPoint> list = getLastList();
            if (list.size() >= 6){
                if (detectionAreaChangeListener != null){
                    detectionAreaChangeListener.overSize();
                }
                return super.onSingleTapConfirmed(e);
            }
            DetectionPoint pointer = new DetectionPoint();
            pointer.x = (int) e.getX();
            pointer.y = (int) e.getY();
            pointer.dx = e.getX();
            pointer.dy = e.getY();
            for (int i = 0; i < list.size(); i++){
                if (samePoint(pointer, list.get(i))){
                    return super.onSingleTapConfirmed(e);
                }
            }

            if (list.size() < 3){
                points.add(pointer);
            }else{
                int index = getInsertIndex(pointer, list);
                if (index == -1){
                    points.add(pointer);
                }else{
                    DetectionPoint p = list.get(index);
                    for (int i = points.size() - 1; i > -1; i--){
                        if (p.x == points.get(i).x && p.y == points.get(i).y){
                            points.add(i, pointer);
                            break;
                        }
                    }
                }
            }

            onChanged();
            invalidate();

            return super.onSingleTapConfirmed(e);
        }
    }

    private boolean intersectLine(DetectionPoint point, List<DetectionPoint> list, int index){
        Line l1 = new Line(list.get(index % list.size()), point);
        Line l2 = new Line(point, list.get((index + 1)%list.size()));

        for (int i = index + 1; i != index; i = (i + 1) % list.size()){
            Line l3 = new Line(list.get(i %list.size()), list.get((i + 1)%list.size()));
            if ((intersectLine(l1, l3)) || intersectLine(l2, l3)){
                return true;
            }
        }

        return false;
    }

    private int getInsertIndex(DetectionPoint point, List<DetectionPoint> list){
        int index = onLine(point, list);
        if (index != -1){
            return index;
        }
        double max = Math.sqrt(Math.pow(point.x - list.get(0).x, 2) + Math.pow(point.y - list.get(0).y,2)) + Math.sqrt(Math.pow(point.x - list.get(list.size() - 1).x, 2) + Math.pow(point.y - list.get(list.size() - 1).y,2));

        for (int i = 0; i < list.size() - 1; i ++){
            double d = Math.sqrt(Math.pow(point.x - list.get(i).x, 2) + Math.pow(point.y - list.get(i).y,2)) +
             Math.sqrt(Math.pow(point.x - list.get(i + 1).x, 2) + Math.pow(point.y - list.get(i + 1).y,2));
            if (d < max){
                index = i;
                max = d;
            }
        }

        if (index == -1){
            index = list.size() - 1;
            if (!intersectLine(point, list, index)){
                return -1;
            }

            double d1 = Math.sqrt(Math.pow(point.x - list.get(0).x, 2) + Math.pow(point.y - list.get(0).y,2)) +
                    Math.sqrt(Math.pow(point.x - list.get(1).x, 2) + Math.pow(point.y - list.get(1).y,2));

            int i = list.size() - 2;
            double d2 = Math.sqrt(Math.pow(point.x - list.get(i).x, 2) + Math.pow(point.y - list.get(i).y,2)) +
                    Math.sqrt(Math.pow(point.x - list.get(i + 1).x, 2) + Math.pow(point.y - list.get(i + 1).y,2));

            if (d1 < d2){
                if (!intersectLine(point, list, 0)){
                    return 1;
                }
                return list.size() - 1;
            }

            if (!intersectLine(point, list, list.size() - 2)){
                return list.size() - 1;
            }
            return 1;

        }

        if (!intersectLine(point, list, index)){
            return index + 1;
        }


        int i = (index + 1) % list.size();;
        int j = (index + 2) % list.size();
        double d1 = Math.sqrt(Math.pow(point.x - list.get(i).x, 2) + Math.pow(point.y - list.get(i).y,2)) +
                Math.sqrt(Math.pow(point.x - list.get(j).x, 2) + Math.pow(point.y - list.get(j).y,2));

        i = index;
        j = (index - 1 + list.size()) % list.size();
        double d2 = Math.sqrt(Math.pow(point.x - list.get(i).x, 2) + Math.pow(point.y - list.get(i).y,2)) +
                Math.sqrt(Math.pow(point.x - list.get(j).x, 2) + Math.pow(point.y - list.get(j).y,2));

        if (d1 < d2){
            if (!intersectLine(point, list, index + 1)){
                return (index + 2) % list.size();
            }
            return index;
        }

        if (!intersectLine(point, list, j)){
            return index;
        }
        return -1;

    }

    private void addPoint(int x, int y){
        DetectionPoint pointer = new DetectionPoint();
        int w = getWidth();
        int h = getHeight();
        if (w == 0){
            w = getContext().getResources().getDisplayMetrics().widthPixels - DimenUtil.dp2px(getContext(), 20);
        }

        if (h == 0){
            h = (int)getContext().getResources().getDimension(R.dimen.detection_area_height);
        }
        pointer.x = w / 2 + x;
        pointer.y = h / 2 + y;
        pointer.dx = pointer.x;
        pointer.dy = pointer.y;

        points.add(pointer);
    }

    public void addNewArea(){
        if (points.size() > 0 && points.get(points.size() - 1).x != -1 && points.get(points.size() - 1).y != -1){
            DetectionPoint pointer = new DetectionPoint();
            pointer.x = -1;
            pointer.y = -1;
            pointer.dx = -1;
            pointer.dy = -1;
            points.add(pointer);
        }


        addPoint(-DimenUtil.dp2px(getContext(), 30), -DimenUtil.dp2px(getContext(), 30));
        addPoint(DimenUtil.dp2px(getContext(), 30), -DimenUtil.dp2px(getContext(), 30));
        addPoint(DimenUtil.dp2px(getContext(), 30), DimenUtil.dp2px(getContext(), 30));
        addPoint(-DimenUtil.dp2px(getContext(), 30), DimenUtil.dp2px(getContext(), 30));

        onChanged();
        invalidate();
    }

    public void reset(){
        points.clear();
        addPoint(-DimenUtil.dp2px(getContext(), 30), -DimenUtil.dp2px(getContext(), 30));
        addPoint(DimenUtil.dp2px(getContext(), 30), -DimenUtil.dp2px(getContext(), 30));
        addPoint(DimenUtil.dp2px(getContext(), 30), DimenUtil.dp2px(getContext(), 30));
        addPoint(-DimenUtil.dp2px(getContext(), 30), DimenUtil.dp2px(getContext(), 30));

        onChanged();
        invalidate();
    }

    private void onChanged(){
        if (detectionAreaChangeListener!=null){
            detectionAreaChangeListener.onChanged();
        }
    }

    public boolean  isOverSize(){
        int count = 0;
        for (int i = 0; points.size() > i; i++) {
            DetectionPoint point = points.get(i);
            if (point.x != -1 && point.y != -1){
                count++;
            }else {
                if (count > 6){
                    return true;
                }
                count = 0;
            }
        }
        return count > 6;
    }

    public boolean  isLessSize(){
        if (points.size() < 3){
            return true;
        }
        int count = 0;
        for (int i = 0; points.size() > i; i++) {
            DetectionPoint point = points.get(i);
            if (point.x != -1 && point.y != -1){
                count++;
            }else {
                if (count < 3 && count > 0){
                    return true;
                }
                count = 0;
            }
        }
        return count < 3 && count > 0;
    }


    public boolean  isOverMax(){
        int count = 1;
        for (int i = 0; points.size() > i; i++) {
            DetectionPoint point = points.get(i);
            if (point.x == -1 && point.y == -1){
                count++;
            }
        }
        return count > 5;
    }


    private boolean samePoint(DetectionPoint p1, DetectionPoint p2) {
        return p1.x == p2.x && p1.y == p2.y;
    }

    private boolean signedArea(DetectionPoint p1, DetectionPoint p2, DetectionPoint p3) {
        return (p1.x - p3.x) * (p2.y - p3.y) > (p2.x - p3.x) * (p1.y - p3.y);
    }

    private boolean intersectLine(Line l1, Line l2) {
        // consecutive edge return false
        if (
                samePoint(l1.start, l2.start) ||
                        samePoint(l1.start, l2.end) ||
                        samePoint(l1.end, l2.start) ||
                        samePoint(l1.end, l2.end)
        ) {
            return false;
        }
        return (
                signedArea(l1.start, l2.start, l2.end) !=
                        signedArea(l1.end, l2.start, l2.end) &&
                        signedArea(l1.start, l1.end, l2.start) !=
                                signedArea(l1.start, l1.end, l2.end)
        );
    }

    private boolean onLine(DetectionPoint point1, DetectionPoint point2, DetectionPoint point){
        int x = Math.abs(point.x - point1.x) + Math.abs(point.x - point2.x);
        if (x != Math.abs(point1.x - point2.x)){
            return false;
        }
        int y = Math.abs(point.y - point1.y) + Math.abs(point.y - point2.y);
        if (y != Math.abs(point1.y - point2.y)){
            return false;
        }
        return (point1.x - point2.x) * (point.y - point2.y) == (point.x - point2.x) * (point1.y - point2.y);

    }
    private int onLine(DetectionPoint point, List<DetectionPoint> list){
        for (int i = 0; i < list.size() - 1; i++){
            if (onLine(list.get(i), list.get(i + 1), point)){
                return i + 1;
            }
        }
        if (onLine(list.get(list.size() - 1), list.get(0), point)){
            return 0;
        }
        return -1;
    }


    private boolean intersectPolygon(List<DetectionPoint> points) {
        int size = points.size();
        for (int i = 0; i < size; i++) {
            for (int j = i + 1; j < size; j++) {
                DetectionPoint p0 = points.get(i);
                DetectionPoint p1 = points.get((i + 1) % size);
                DetectionPoint p2 = points.get(j);
                DetectionPoint p3 = points.get((j + 1) % size);
                Line line = new Line(p0, p1);
                Line line1 = new Line(p2, p3);
                if (intersectLine(line, line1)) {
                    return true;
                }

            }


        }
        return false;
    }

    static class Line {
        DetectionPoint start;
        DetectionPoint end;

        public Line(DetectionPoint start, DetectionPoint end) {
            this.start = start;
            this.end = end;
        }
    }
    public interface  DetectionAreaChangeListener{
        void onChanged();
        void overSize();
    }

}
